﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Navigation;
using SilverlightTetris_M10CDE.TetrisRef;
using System.Threading;

namespace SilverlightTetris_M10CDE.Views
{
    public partial class Home : Page
    {
        //Creating new Web service reference object
        TetrisRef.TetrisServiceSoapClient client = new TetrisRef.TetrisServiceSoapClient();

        //Create values to store game score information (parameters used when submitting score information)
        String playerName;
        String score;
        String lines;
        String level;
        String playTime;
        String date;

        //Create two seperate timers, one for the game speed the other for the time played
        System.Windows.Threading.DispatcherTimer gameSpeed = new System.Windows.Threading.DispatcherTimer();
        System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();

        int i = 1;//seconds
        int j = 1;//minutes

        bool gameInProgress;//to check if game has been initiated
        bool gamePaused; //to check if game is paused so that we can block user input while game is paused
        bool gameOver; //to check if the game has finished so it can be restarted

        Rectangle[] myBlocks;//holds the rectangle objects for the active shape
        Rectangle[] myNextBlocks;//holds the rectangle objects for the next shape

        //representation of the blocks of the current shape
        Rectangle block1;
        Rectangle block2;
        Rectangle block3;
        Rectangle block4;

        //representation of the blocks of the next shape
        Rectangle nextblock1;
        Rectangle nextblock2;
        Rectangle nextblock3;
        Rectangle nextblock4;

        bool lockit = true;//ensures methods get executed on the right order 
        bool shapeGenerated = false;//ensures methods get executed on the right order 
        bool downInput = false;

        List<Rectangle> myRectangeList = new List<Rectangle>();//Stores list of rectangles for the current shape
        List<Rectangle> myNextRectangeList = new List<Rectangle>();//Stores list of rectangles for the next shape

        /// <summary>
        /// Constructor
        /// </summary>
        public Home()
        {
            InitializeComponent();
            myBlocks = new Rectangle[4];
            myNextBlocks = new Rectangle[4];

            client.StartControllerAsync(); //calls the service method that creates a new instance of the game and saves its state
        }

        /// <summary>
        /// Executes when the user navigates to this page
        /// </summary>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {

        }

        /// <summary>
        /// Start button. Starts the game execution.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            gamePaused = false;
            pauseMask.Visibility = Visibility.Collapsed;
            pauseMessage.Visibility = Visibility.Collapsed;
            levelLabel.Text = "1";

            if (!gameInProgress)//if the game is not in progress than create a new instance of it
            {
                gameInProgress = true;

                gameSpeed.Interval = new TimeSpan(0, 0, 0, 1, 0);
                gameSpeed.Tick += new EventHandler(Each_Game_Tick);
                gameSpeed.Start();

                timer.Interval = new TimeSpan(0, 0, 0, 1, 0);
                timer.Tick += new EventHandler(Each_Sec_Tick);
                timer.Start();

                client.generateShapeCompleted += (sen, ea) =>//Completed event that is raised everytime a new shape is generated
                {
                    shapeGenerated = true;

                    client.checkGameOverCompleted += (se, re) =>//Completed event that is raised to check if the game has finished yet
                    {
                        Boolean b = re.Result;
                        if (b == false)
                        {
                            timer.Stop();
                            gameSpeed.Stop();

                            gameInProgress = false;
                            gameOver = true;
                            gameover.Visibility = Visibility.Visible;

                            submitscorepoints.Text = " " + scoreLabel.Text + " points";

                            //reset blocks from game field
                            List<Rectangle> rect = GridLocate.getAllBlocks(gameBoard);

                            foreach (Rectangle r in rect)
                            {
                                gameBoard.Children.Remove(r);
                            }

                            submitedName.Focus();

                            submitscore.Visibility = Visibility.Visible;

                            Start.Content = "Restart";

                            Start.IsEnabled = true;
                            Start.Opacity = 1;

                            Pause.IsEnabled = false;
                            Pause.Opacity = 0.5;
                        }
                    };

                    client.checkGameOverAsync();//Call to web method to check if game has finished yet

                };

                client.generateShapeAsync();//call to web method that generates a new shape

                client.getShapeCompleted += new EventHandler<TetrisRef.getShapeCompletedEventArgs>(getShapeCompleted);//Define event handler for the on completed event of get current shape. 

                client.getShapeAsync();//call to web method that retrieves coordinates of the current active shape

                client.getNextShapeCompleted += new EventHandler<TetrisRef.getNextShapeCompletedEventArgs>(getNextShapeCompleted);//Define event handler for the on completed event of get next shape. 

                client.getNextShapeAsync();//Call to web method that retrieves coordinates of the next shape

                Start.IsEnabled = false;
                Start.Opacity = 0.5;
                Start.Content = "Resume";//changes name of start button to resume

                gameover.Visibility = Visibility.Collapsed;
                submitscore.Visibility = Visibility.Collapsed;
            }
            else
            {
                timer.Start();
                Start.IsEnabled = false;
                Start.Opacity = 0.5;

                Pause.IsEnabled = true;
                Pause.Opacity = 1;
            }

            timer.Start();
            gameSpeed.Start();
        }

        /// <summary>
        /// Everytime the timer ticks (Each second as default for level 1) execute gravityMove method
        /// </summary>
        /// <param name="o"></param>
        /// <param name="sender"></param>
        public void Each_Game_Tick(object o, EventArgs sender)
        {
            gravityMove();
        }

        /// <summary>
        /// Moves each of the current shape blocks down 1 block.
        /// If shape was able to move than update its current location on the board.
        /// If shape is unable to move than dock it, and check if any lines can be removed. If yes then update grid accordingly
        /// </summary>
        public void gravityMove()
        {
            //If input comes from pressing key down then only execute a portion of thise requests to avoid crashes
            Random rand = new Random();
            int myRand = rand.Next(1, 500);

            if (downInput == true)
            {
                if (myRand == 1)
                {
                    downInput = false;
                }
            }
            if (gamePaused == false && downInput == false)
            {
                client.gravityMoveCompleted += (sen, ea) =>
                {
                    if (ea.Result == true)
                    {
                        client.getShapeAsync(2);//Using user state allows to execute only a particular block of code inside the on completed event of get shape. In this case we only call code to update the block position.
                    }
                    else if (ea.Result == false)
                    {
                        if (lockit == true)
                        {
                            for (int i = 17; i > 0; i--)
                            {
                                List<Rectangle> rect = GridLocate.getLineBlocks(gameBoard, i);//Run helper method to get reference of all rectangles on the grid that meet the specified criteria

                                if (rect.Count == 15)//if a line is completed...
                                {
                                    foreach (Rectangle r in rect)
                                    {
                                        gameBoard.Children.Remove(r);//...remove the blocks
                                        i = 18;//if a line is removed then start couting from begining
                                    }

                                    List<Rectangle> otherRect = GridLocate.getAllOtherLineBlocks(gameBoard, i);

                                    foreach (Rectangle r in otherRect)//All other rectangles not removed shift a place down on the grid
                                    {
                                        r.SetValue(Grid.RowProperty, (Int32)r.GetValue(Grid.RowProperty) + 1);
                                    }

                                    client.updateScoreCompleted += (sender, e) =>//update score
                                    {
                                        scoreLabel.Text = e.Result.ToString();
                                        scoreAnime.Begin();
                                        scoreAnime2.Begin();
                                    };

                                    client.updateScoreAsync();

                                    client.updateLinesCompleted += (sender, e) =>//update lines removed
                                    {
                                        linesLabel.Text = e.Result.ToString();
                                        linesAnime.Begin();
                                        linesAnime2.Begin();
                                    };

                                    client.updateLinesAsync();
                                }
                            }
                            client.generateShapeAsync();
                            client.getShapeAsync(3);
                            lockit = false;
                        }
                        client.getNextShapeAsync();
                    }
                    downInput = false;
                };
                client.gravityMoveAsync();
            }
        }

        /// <summary>
        /// Move the current shape position to the left and get its new position from the service
        /// </summary>
        public void moveLeft()
        {
            client.moveLeftAsync();

            client.moveLeftCompleted += (sen, ea) =>
            {
                client.getShapeAsync(2);
            };
        }

        /// <summary>
        /// Move the current shape position to the right and get its new position from the service
        /// </summary>
        public void moveRight()
        {
            client.moveRightAsync();

            client.moveRightCompleted += (sen, ea) =>
            {
                client.getShapeAsync(2);
            };
        }

        /// <summary>
        /// Rotate the current shape and get its new position from the service
        /// </summary>
        public void rotateShape()
        {
            client.rotateShapeAsync();

            client.rotateShapeCompleted += (sen, ea) =>
            {
                client.getShapeAsync(2);
            };
        }

        /// <summary>
        /// Defines the code that gets executed once the results of the current shape position are retrieved from the service.
        /// This involves returning the new shape coordinate, and re-assigning it to each of the block of the shape
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ea"></param>
        private void getShapeCompleted(object sender, getShapeCompletedEventArgs ea)
        {
            if (shapeGenerated == true && gameOver == false)
            {
                block1 = new Rectangle();
                block2 = new Rectangle();
                block3 = new Rectangle();
                block4 = new Rectangle();

                myRectangeList.Clear();
                myRectangeList.Add(block1);
                myRectangeList.Add(block2);
                myRectangeList.Add(block3);
                myRectangeList.Add(block4);

                int i = 0;
                foreach (Rectangle x in myRectangeList)
                {
                    x.Height = 24;
                    x.Width = 24;
                    x.Stroke = new SolidColorBrush(Colors.Black);
                    gameBoard.Children.Add(x);

                    myBlocks[i] = x;

                    myBlocks[i].SetValue(Grid.RowProperty, ea.Result[i][1]);
                    myBlocks[i].SetValue(Grid.ColumnProperty, ea.Result[i][0]);
                    i++;
                }

                shapeGenerated = false;

                //Create a second thread responsible for populating each block with its respective color sent from the service. 
                //This 2nd thread reduces slightly the game latency due to UI Thread getting overloaded with requests from service.
                Thread t = new Thread((o) =>
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        client.getCurrentShapeColorCompleted += (s, e) =>
                        {
                            String colorName = e.Result;
                            foreach (Rectangle z in myRectangeList)
                            {
                                if (colorName == "Red")
                                    z.Fill = new SolidColorBrush(Colors.Red);
                                else if (colorName == "Blue")
                                    z.Fill = new SolidColorBrush(Colors.Blue);
                                else if (colorName == "Orange")
                                    z.Fill = new SolidColorBrush(Colors.Orange);
                                else if (colorName == "Yellow")
                                    z.Fill = new SolidColorBrush(Colors.Yellow);
                                else if (colorName == "Green")
                                    z.Fill = new SolidColorBrush(Colors.Green);
                                else if (colorName == "LightGray")
                                    z.Fill = new SolidColorBrush(Colors.LightGray);
                                else if (colorName == "Purple")
                                    z.Fill = new SolidColorBrush(Colors.Purple);
                            }
                        };
                        client.getCurrentShapeColorAsync();
                    });
                });
                t.Start();
            }

            else if ((Int32)ea.UserState == 2)
            {
                //Delay the amount of times the screen updates (to increase playability and reduce latency)
                Random rand = new Random();
                int myRand = rand.Next(1, 2);

                bool delay = true;

                if (delay == true)
                {
                    if (myRand == 1)
                    {
                        delay = false;
                    }
                }

                if (delay == false)
                {
                    int c = 0;
                    foreach (Rectangle x in myBlocks)
                    {
                        myBlocks[c] = x;

                        myBlocks[c].SetValue(Grid.RowProperty, ea.Result[c][1]);
                        myBlocks[c].SetValue(Grid.ColumnProperty, ea.Result[c][0]);

                        c++;
                    }
                    lockit = true;
                }
            }

            else if ((Int32)ea.UserState == 3)
            {
                if (shapeGenerated == true && gameOver == false)
                {
                    block1 = new Rectangle();
                    block2 = new Rectangle();
                    block3 = new Rectangle();
                    block4 = new Rectangle();

                    myRectangeList.Clear();
                    myRectangeList.Add(block1);
                    myRectangeList.Add(block2);
                    myRectangeList.Add(block3);
                    myRectangeList.Add(block4);

                    int i = 0;
                    foreach (Rectangle x in myRectangeList)
                    {
                        x.Height = 24;
                        x.Width = 24;
                        x.Fill = new SolidColorBrush(Colors.Red);
                        x.Stroke = new SolidColorBrush(Colors.Black);
                        gameBoard.Children.Add(x);

                        myBlocks[i] = null;
                        myBlocks[i] = x;

                        myBlocks[i].SetValue(Grid.RowProperty, ea.Result[i][1]);
                        myBlocks[i].SetValue(Grid.ColumnProperty, ea.Result[i][0]);
                        i++;
                    }
                    shapeGenerated = false;
                }
            }
        }

        /// <summary>
        /// Defines the code that gets executed once the results of the next shape position are retrieved from the service.
        /// This involves returning the new shape coordinate, and re-assigning it to each of the block of the shape
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="rs"></param>
        private void getNextShapeCompleted(object sender, getNextShapeCompletedEventArgs rs)
        {
            if (gameOver == false)
            {
                foreach (Rectangle r in myNextRectangeList)
                {
                    nextShapeBoard.Children.Remove(r);
                }

                //create next shape blocks
                nextblock1 = new Rectangle();
                nextblock2 = new Rectangle();
                nextblock3 = new Rectangle();
                nextblock4 = new Rectangle();

                myNextRectangeList.Clear();
                myNextRectangeList.Add(nextblock1);
                myNextRectangeList.Add(nextblock2);
                myNextRectangeList.Add(nextblock3);
                myNextRectangeList.Add(nextblock4);

                int b = 0;

                //instantiate block attributes for next shape and place them on the next shape container
                foreach (Rectangle r in myNextRectangeList)
                {
                    r.Height = 24;
                    r.Width = 24;

                    r.Stroke = new SolidColorBrush(Colors.Black);
                    nextShapeBoard.Children.Add(r);

                    myNextBlocks[b] = r;

                    r.SetValue(Grid.RowProperty, rs.Result[b][1]);
                    r.SetValue(Grid.ColumnProperty, rs.Result[b][0]);
                    b++;
                }

                Thread t = new Thread((o) =>
                {
                    Deployment.Current.Dispatcher.BeginInvoke(() =>
                    {
                        client.getNextShapeColorCompleted += (s, e) =>
                        {
                            String colorName = e.Result;
                            foreach (Rectangle z in myNextRectangeList)
                            {
                                if (colorName == "Red")
                                    z.Fill = new SolidColorBrush(Colors.Red);
                                else if (colorName == "Blue")
                                    z.Fill = new SolidColorBrush(Colors.Blue);
                                else if (colorName == "Orange")
                                    z.Fill = new SolidColorBrush(Colors.Orange);
                                else if (colorName == "Yellow")
                                    z.Fill = new SolidColorBrush(Colors.Yellow);
                                else if (colorName == "Green")
                                    z.Fill = new SolidColorBrush(Colors.Green);
                                else if (colorName == "LightGray")
                                    z.Fill = new SolidColorBrush(Colors.LightGray);
                                else if (colorName == "Purple")
                                    z.Fill = new SolidColorBrush(Colors.Purple);
                            }
                        };
                        client.getNextShapeColorAsync();
                    });
                });

                t.Start();
            }
        }

        /// <summary>
        /// Simply updates the game played time label accordingly
        /// </summary>
        /// <param name="o"></param>
        /// <param name="sender"></param>
        public void Each_Sec_Tick(object o, EventArgs sender)
        {
            if (i < 10)
            {
                timeSec.Text = "0" + i.ToString();
                i++;
            }
            else
            {
                timeSec.Text = i.ToString();
                i++;
            }

            if (i == 61)
            {
                timeSec.Text = "00";
                i = 1;
                if (j < 10)
                {
                    timeMin.Text = "0" + j.ToString() + ":";
                    j++;
                }
                else
                {
                    timeMin.Text = j.ToString() + ":";
                    j++;
                }
            }
        }

        /// <summary>
        /// Pause button. pauses the game and hides the current game blocks to avoid cheating!
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            timer.Stop();
            gameSpeed.Stop();

            Pause.IsEnabled = false;
            Pause.Opacity = 0.5;

            Start.IsEnabled = true;
            Start.Opacity = 1;

            pauseMask.Visibility = Visibility.Visible;
            pauseMessage.Visibility = Visibility.Visible;

            gamePaused = true;
        }

        /// <summary>
        /// Handles user input
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_KeyDown(object sender, KeyEventArgs e)
        {
            if (gameInProgress && gamePaused == false)
            {
                switch (e.Key)
                {
                    case Key.Left:
                        moveLeft();
                        break;
                    case Key.Right:
                        moveRight();
                        break;
                    case Key.Up:
                        rotateShape();
                        break;
                    case Key.Down:
                        gravityMove();
                        downInput = true;
                        break;
                }
            }
        }

        /// <summary>
        /// Submit score button. Encrypts each of the score parameters to be passed to the web service before actually sending them.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            string key = "tetris";

            playerName = submitedName.Text;
            score = scoreLabel.Text;
            lines = linesLabel.Text;
            level = levelLabel.Text;
            playTime = i.ToString();
            date = DateTime.Now.ToString();

            client.encryptStringCompleted += new EventHandler<TetrisRef.encryptStringCompletedEventArgs>(client_encryptionString);
            client.encryptStringAsync(playerName, key, 1);
            client.encryptStringAsync(score, key, 2);
            client.encryptStringAsync(lines, key, 3);
            client.encryptStringAsync(level, key, 4);
            client.encryptStringAsync(playTime, key, 5);
            client.encryptStringAsync(date, key, 6);
            client.encryptStringAsync("x", "x", 7);//used only to enforce right method ordering so that addScoreMethod only gets executed after all the parameters have been encrypted

            gameover.Visibility = Visibility.Collapsed;
            submitscore.Visibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Encrypts each of the score parameters
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void client_encryptionString(object sender, encryptStringCompletedEventArgs e)
        {
            if ((Int32)e.UserState == 1)
                playerName = e.Result.ToString();

            else if ((Int32)e.UserState == 2)
                score = e.Result.ToString();

            else if ((Int32)e.UserState == 3)
                lines = e.Result.ToString();

            else if ((Int32)e.UserState == 4)
                level = e.Result.ToString();

            else if ((Int32)e.UserState == 5)
                playTime = e.Result.ToString();

            else if ((Int32)e.UserState == 6)
            {
                date = e.Result.ToString();
            }
            else if ((Int32)e.UserState == 7)
            {
                client.addScoreCompleted += (s, re) =>
                {
                    this.NavigationService.Navigate(new Uri("/Highscores", UriKind.Relative));
                };

                client.addScoreAsync(playerName, score, lines, level, playTime, date);
            }
        }
    }
}
